﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Brainee.NeuralNetwork
{
  public class Neuron : IEnumerable<double>
  {
    private static Random _random = new Random ((int)DateTime.Now.Ticks);

    #region Private fields

    // Dendrites and synapses (input)
    private double [] _weights;

    // Soma / nucleus (bias and activation)
    private double _threshold;
    private IActivationFunction _activation;

    // Axon (output)
    protected double _value;

    #endregion
    #region Public properties and indexers

    public double this [int weightIndex]
    {
      get { return _weights [weightIndex]; }
      set { _weights [weightIndex] = value; }
    }

    public double Threshold
    {
      get { return _threshold; }
      set { _threshold = value; }
    }

    public int Size { get { return _weights.Length; } }

    public double Value { get { return _value; } }

    public IActivationFunction Activation { get { return _activation; } }

    #endregion
    #region Construction

    public Neuron (IActivationFunction tf, int inputs)
    {
      _activation = tf;
      _weights = new double [inputs];
      Randomize ();
    }

    public Neuron (IActivationFunction tf, double [] weights)
    {
      _activation = tf;
      _threshold = 0.0;
      _weights = weights;
    }

    public void Randomize ()
    {
      _threshold = _random.NextDouble () - 0.5;
      for (int i = 0; i < _weights.Length; i++)
      {
        _weights [i] = _random.NextDouble () - 0.5;
      }
    }

    #endregion
    #region Evaluation

    public virtual double Compute (double [] stim)
    {
      if (stim.Length != _weights.Length)
        throw new ArgumentException ("The stimulus must be the same size that the inputs of the neuron");

      double result = _threshold;

      for (int i = 0; i < _weights.Length; i++)
      {
        result += _weights [i] * stim [i];
      }

      _value = _activation.Invoke (result);

      return _value;
    }

    #endregion
    #region IEnumerable<double> Members

    public IEnumerator<double> GetEnumerator ()
    {
      return ((IEnumerable<double>)_weights).GetEnumerator ();
    }

    #endregion
    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator ()
    {
      return _weights.GetEnumerator ();
    }

    #endregion
  }

  public class InputNeuron : Neuron
  {
    private int _index;

    public InputNeuron (int index)
      : base (new IdentityFunction (), 1)
    {
      _index = index;
    }

    public override double Compute (double [] stim)
    {
      _value = stim[_index];

      return _value;
    }
  }
}
